Skip to content

status: add --json output format to git status #1937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

tacheraSasi
Copy link

@tacheraSasi tacheraSasi commented Jun 23, 2025

cc: Phillip Wood [email protected]

Copy link

gitgitgadget bot commented Jun 23, 2025

Welcome to GitGitGadget

Hi @tacheraSasi, and welcome to GitGitGadget, the GitHub App to send patch series to the Git mailing list from GitHub Pull Requests.

Please make sure that either:

  • Your Pull Request has a good description, if it consists of multiple commits, as it will be used as cover letter.
  • Your Pull Request description is empty, if it consists of a single commit, as the commit message should be descriptive enough by itself.

You can CC potential reviewers by adding a footer to the PR description with the following syntax:

CC: Revi Ewer <[email protected]>, Ill Takalook <[email protected]>

NOTE: DO NOT copy/paste your CC list from a previous GGG PR's description,
because it will result in a malformed CC list on the mailing list. See
example.

Also, it is a good idea to review the commit messages one last time, as the Git project expects them in a quite specific form:

  • the lines should not exceed 76 columns,
  • the first line should be like a header and typically start with a prefix like "tests:" or "revisions:" to state which subsystem the change is about, and
  • the commit messages' body should be describing the "why?" of the change.
  • Finally, the commit messages should end in a Signed-off-by: line matching the commits' author.

It is in general a good idea to await the automated test ("Checks") in this Pull Request before contributing the patches, e.g. to avoid trivial issues such as unportable code.

Contributing the patches

Before you can contribute the patches, your GitHub username needs to be added to the list of permitted users. Any already-permitted user can do that, by adding a comment to your PR of the form /allow. A good way to find other contributors is to locate recent pull requests where someone has been /allowed:

Both the person who commented /allow and the PR author are able to /allow you.

An alternative is the channel #git-devel on the Libera Chat IRC network:

<newcontributor> I've just created my first PR, could someone please /allow me? https://github.com/gitgitgadget/git/pull/12345
<veteran> newcontributor: it is done
<newcontributor> thanks!

Once on the list of permitted usernames, you can contribute the patches to the Git mailing list by adding a PR comment /submit.

If you want to see what email(s) would be sent for a /submit request, add a PR comment /preview to have the email(s) sent to you. You must have a public GitHub email address for this. Note that any reviewers CC'd via the list in the PR description will not actually be sent emails.

After you submit, GitGitGadget will respond with another comment that contains the link to the cover letter mail in the Git mailing list archive. Please make sure to monitor the discussion in that thread and to address comments and suggestions (while the comments and suggestions will be mirrored into the PR by GitGitGadget, you will still want to reply via mail).

If you do not want to subscribe to the Git mailing list just to be able to respond to a mail, you can download the mbox from the Git mailing list archive (click the (raw) link), then import it into your mail program. If you use GMail, you can do this via:

curl -g --user "<EMailAddress>:<Password>" \
    --url "imaps://imap.gmail.com/INBOX" -T /path/to/raw.txt

To iterate on your change, i.e. send a revised patch or patch series, you will first want to (force-)push to the same branch. You probably also want to modify your Pull Request description (or title). It is a good idea to summarize the revision by adding something like this to the cover letter (read: by editing the first comment on the PR, i.e. the PR description):

Changes since v1:
- Fixed a typo in the commit message (found by ...)
- Added a code comment to ... as suggested by ...
...

To send a new iteration, just add another PR comment with the contents: /submit.

Need help?

New contributors who want advice are encouraged to join [email protected], where volunteers who regularly contribute to Git are willing to answer newbie questions, give advice, or otherwise provide mentoring to interested contributors. You must join in order to post or view messages, but anyone can join.

You may also be able to find help in real time in the developer IRC channel, #git-devel on Libera Chat. Remember that IRC does not support offline messaging, so if you send someone a private message and log out, they cannot respond to you. The scrollback of #git-devel is archived, though.

@dscho
Copy link
Member

dscho commented Jun 23, 2025

/allow

Copy link

gitgitgadget bot commented Jun 23, 2025

User tacheraSasi is now allowed to use GitGitGadget.

WARNING: tacheraSasi has no public email address set on GitHub; GitGitGadget needs an email address to Cc: you on your contribution, so that you receive any feedback on the Git mailing list. Go to https://github.com/settings/profile to make your preferred email public to let GitGitGadget know which email address to use.

Add a new --json flag to 'git status' that outputs repository state
in a structured JSON format. This enables reliable machine parsing
of status information for tools and automation.

The JSON output includes:
- Branch information (name, detached state, ahead/behind counts)
- Staged files array
- Unstaged files array
- Untracked files array
- Ignored files array (with --ignored flag)

Implementation details:
- Add STATUS_FORMAT_JSON to wt_status_format enum
- Add JSON output option to git status and git commit
- Implement JSON formatting helpers for arrays and branch info
- Structure output for easy parsing and future extensibility

Example:
  $ git status --json
  {
    "branch": {
      "current": "main",
      "detached": false
    },
    "staged": ["file1.txt"],
    "unstaged": ["file2.txt"],
    "untracked": ["file3.txt"]
  }

This provides a robust alternative to parsing traditional output
formats, making it easier to build reliable tools and automation
around Git status information.

Signed-off-by: Tachera Sasi <[email protected]>
Signed-off-by: tacherasasi <[email protected]>
@tacheraSasi tacheraSasi force-pushed the feature/json-status-output branch from 98333d5 to d6d4b3e Compare July 28, 2025 11:57
Copy link
Author

@tacheraSasi tacheraSasi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@tacheraSasi
Copy link
Author

/preview

Copy link

gitgitgadget bot commented Jul 30, 2025

Preview email sent as [email protected]

@tacheraSasi
Copy link
Author

/submit

Copy link

gitgitgadget bot commented Jul 30, 2025

Submitted as [email protected]

To fetch this version into FETCH_HEAD:

git fetch https://github.com/gitgitgadget/git/ pr-1937/tacheraSasi/feature/json-status-output-v1

To fetch this version to local tag pr-1937/tacheraSasi/feature/json-status-output-v1:

git fetch --no-tags https://github.com/gitgitgadget/git/ tag pr-1937/tacheraSasi/feature/json-status-output-v1

Copy link

gitgitgadget bot commented Jul 30, 2025

On the Git mailing list, Phillip Wood wrote (reply to this):

On 30/07/2025 07:27, Tach via GitGitGadget wrote:
> From: tacherasasi <[email protected]>
> > Add a new --json flag to 'git status' that outputs repository state
> in a structured JSON format. This enables reliable machine parsing
> of status information for tools and automation.
> > The JSON output includes:
> - Branch information (name, detached state, ahead/behind counts)
> - Staged files array
> - Unstaged files array
> - Untracked files array
> - Ignored files array (with --ignored flag)
> > Implementation details:
> - Add STATUS_FORMAT_JSON to wt_status_format enum
> - Add JSON output option to git status and git commit
> - Implement JSON formatting helpers for arrays and branch info
> - Structure output for easy parsing and future extensibility
> > Example:
>    $ git status --json
>    {
>      "branch": {
>        "current": "main",
>        "detached": false
>      },
>      "staged": ["file1.txt"],
>      "unstaged": ["file2.txt"],
>      "untracked": ["file3.txt"]
>    }
> > This provides a robust alternative to parsing traditional output
> formats, making it easier to build reliable tools and automation
> around Git status information.

What problem are you having paring the current output? You say this patch provides a robust alternative but I'm afraid it does not seem to handle filenames containing newlines or double quotes and it assumes they are utf-8 encoded. It also seems to print a trailing comma if an array has more than one item which is not allowed by the json spec. We already have support for generating json output as documented json-writer.h which will help with most of that but not the filename encoding problem. New features should also be accompanied by tests to prevent future regressions. I'm not opposed to adding json output if it is robust and handles non utf-8 filenames but it would be helpful to understand what problems you are having parsing the output of "git status --porcelain[=v2]".

Thanks

Phillip

> Signed-off-by: Tachera Sasi <[email protected]>
> Signed-off-by: tacherasasi <[email protected]>
> ---
>      status: add --json output format to git status
> > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1937%2FtacheraSasi%2Ffeature%2Fjson-status-output-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1937/tacheraSasi/feature/json-status-output-v1
> Pull-Request: https://github.com/gitgitgadget/git/pull/1937
> >   builtin/commit.c |  9 ++++-
>   wt-status.c      | 98 ++++++++++++++++++++++++++++++++++++++++++++++++
>   wt-status.h      |  1 +
>   3 files changed, 107 insertions(+), 1 deletion(-)
> > diff --git a/builtin/commit.c b/builtin/commit.c
> index fba0dded64a..f1db4fdfd9a 100644
> --- a/builtin/commit.c
> +++ b/builtin/commit.c
> @@ -1540,6 +1540,9 @@ struct repository *repo UNUSED)
>   		OPT_SET_INT(0, "long", &status_format,
>   			    N_("show status in long format (default)"),
>   			    STATUS_FORMAT_LONG),
> +		OPT_SET_INT(0, "json", &status_format,
> +		    N_("show status in JSON format"),
> +		    STATUS_FORMAT_JSON),
>   		OPT_BOOL('z', "null", &s.null_termination,
>   			 N_("terminate entries with NUL")),
>   		{
> @@ -1603,7 +1606,8 @@ struct repository *repo UNUSED)
>   		       prefix, argv);
>   >   	if (status_format != STATUS_FORMAT_PORCELAIN &&
> -	    status_format != STATUS_FORMAT_PORCELAIN_V2)
> +	    status_format != STATUS_FORMAT_PORCELAIN_V2 &&
> +	    status_format != STATUS_FORMAT_JSON)
>   		progress_flag = REFRESH_PROGRESS;
>   	repo_read_index(the_repository);
>   	refresh_index(the_repository->index,
> @@ -1735,6 +1739,9 @@ int cmd_commit(int argc,
>   		OPT_SET_INT(0, "long", &status_format,
>   			    N_("show status in long format (default)"),
>   			    STATUS_FORMAT_LONG),
> +		OPT_SET_INT(0, "json", &status_format,
> +		    N_("show status in JSON format"),
> +		    STATUS_FORMAT_JSON),
>   		OPT_BOOL('z', "null", &s.null_termination,
>   			 N_("terminate entries with NUL")),
>   		OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
> diff --git a/wt-status.c b/wt-status.c
> index 454601afa15..7192fb4d057 100644
> --- a/wt-status.c
> +++ b/wt-status.c
> @@ -2564,6 +2564,101 @@ static void wt_porcelain_v2_print(struct wt_status *s)
>   	}
>   }
>   > +
> +static void wt_json_print_string_array(struct wt_status *s, const char *name, struct string_list *list)
> +{
> +	int i;
> +	fprintf(s->fp, "  \"%s\": [", name);
> +	for (i = 0; i < list->nr; i++) {
> +		if (i > 0)
> +			fprintf(s->fp, ", ");
> +		fprintf(s->fp, "\"%s\"", list->items[i].string);
> +	}
> +	fprintf(s->fp, "]");
> +}
> +
> +static void wt_json_print_change_array(struct wt_status *s, const char *name, int change_type)
> +{
> +	int i;
> +	struct string_list files = STRING_LIST_INIT_DUP;
> +
> +	for (i = 0; i < s->change.nr; i++) {
> +		struct wt_status_change_data *d;
> +		struct string_list_item *it;
> +		it = &(s->change.items[i]);
> +		d = it->util;
> +
> +		if ((change_type == WT_STATUS_UPDATED && d->index_status &&
> +		     d->index_status != DIFF_STATUS_UNMERGED) ||
> +		    (change_type == WT_STATUS_CHANGED && d->worktree_status &&
> +		     d->worktree_status != DIFF_STATUS_UNMERGED)) {
> +			string_list_append(&files, it->string);
> +		}
> +	}
> +
> +	wt_json_print_string_array(s, name, &files);
> +	string_list_clear(&files, 0);
> +}
> +
> +static void wt_json_print_branch_info(struct wt_status *s)
> +{
> +	struct branch *branch;
> +	const char *branch_name;
> +	int ahead = 0, behind = 0;
> +
> +	fprintf(s->fp, "  \"branch\": {\n");
> +
> +	if (s->branch && !s->is_initial) {
> +		if (!strcmp(s->branch, "HEAD")) {
> +			fprintf(s->fp, "    \"current\": \"HEAD\",\n");
> +			fprintf(s->fp, "    \"detached\": true");
> +		} else {
> +			if (skip_prefix(s->branch, "refs/heads/", &branch_name)) {
> +				fprintf(s->fp, "    \"current\": \"%s\",\n", branch_name);
> +				fprintf(s->fp, "    \"detached\": false");
> +
> +				branch = branch_get(branch_name);
> +				if (branch && branch->merge && branch->merge[0] && branch->merge[0]->dst) {
> +					if (!stat_tracking_info(branch, &ahead, &behind, NULL, 0, 0)) {
> +						fprintf(s->fp, ",\n    \"ahead\": %d,\n    \"behind\": %d", ahead, behind);
> +					}
> +				}
> +			} else {
> +				fprintf(s->fp, "    \"current\": \"%s\",\n", s->branch);
> +				fprintf(s->fp, "    \"detached\": false");
> +			}
> +		}
> +	} else {
> +		fprintf(s->fp, "    \"current\": null,\n");
> +		fprintf(s->fp, "    \"detached\": false");
> +	}
> +
> +	fprintf(s->fp, "\n  }");
> +}
> +
> +static void wt_json_status_print(struct wt_status *s)
> +{
> +	fprintf(s->fp, "{\n");
> +
> +	wt_json_print_branch_info(s);
> +	fprintf(s->fp, ",\n");
> +
> +	wt_json_print_change_array(s, "staged", WT_STATUS_UPDATED);
> +	fprintf(s->fp, ",\n");
> +
> +	wt_json_print_change_array(s, "unstaged", WT_STATUS_CHANGED);
> +	fprintf(s->fp, ",\n");
> +
> +	wt_json_print_string_array(s, "untracked", &s->untracked);
> +
> +	if (s->ignored.nr > 0) {
> +		fprintf(s->fp, ",\n");
> +		wt_json_print_string_array(s, "ignored", &s->ignored);
> +	}
> +
> +	fprintf(s->fp, "\n}\n");
> +}
> +
>   void wt_status_print(struct wt_status *s)
>   {
>   	trace2_data_intmax("status", s->repo, "count/changed", s->change.nr);
> @@ -2583,6 +2678,9 @@ void wt_status_print(struct wt_status *s)
>   	case STATUS_FORMAT_PORCELAIN_V2:
>   		wt_porcelain_v2_print(s);
>   		break;
> +	case STATUS_FORMAT_JSON:
> +		wt_json_status_print(s);
> +		break;
>   	case STATUS_FORMAT_UNSPECIFIED:
>   		BUG("finalize_deferred_config() should have been called");
>   		break;
> diff --git a/wt-status.h b/wt-status.h
> index 4e377ce62b8..e929af832b2 100644
> --- a/wt-status.h
> +++ b/wt-status.h
> @@ -74,6 +74,7 @@ enum wt_status_format {
>   	STATUS_FORMAT_SHORT,
>   	STATUS_FORMAT_PORCELAIN,
>   	STATUS_FORMAT_PORCELAIN_V2,
> +	STATUS_FORMAT_JSON,
>   >   	STATUS_FORMAT_UNSPECIFIED
>   };
> > base-commit: cb3b40381e1d5ee32dde96521ad7cfd68eb308a6

Copy link

gitgitgadget bot commented Jul 30, 2025

User Phillip Wood <[email protected]> has been added to the cc: list.

Copy link

gitgitgadget bot commented Jul 30, 2025

On the Git mailing list, [email protected] wrote (reply to this):

Hi Tachera

On 30/07/2025 10:15, Tachera W sasi wrote:
> > For v2, I will:
>    * Rework the patch to use `json-writer.h` so escaping and JSON > compliance are handled
>      properly.

That will handle the escaping problems but we still have the problem that filenames and refnames are not guaranteed to be utf-8 encoded. I think the only way to handle that is to base64 encode them so that the caller can retrieve the raw bytes. See https://lore.kernel.org/git/[email protected]/ for a previous discussion about this.

>    * Add tests covering filenames with quotes, newlines, and non-UTF-8 > cases.
>    * Include tests under `t/` to prevent future regressions.

That's great, you should update Documentation/git-status.adoc as well to document the new option and output format

Thanks

Phillip

> Thanks again for your review and for pointing me towards the proper helpers.
> > Best regards,
> Tachera Sasi
> > On Wed, Jul 30, 2025 at 12:01 PM Phillip Wood <[email protected] > <mailto:[email protected]>> wrote:
> >     On 30/07/2025 07:27, Tach via GitGitGadget wrote:
>      > From: tacherasasi <[email protected]
>     <mailto:[email protected]>>
>      >
>      > Add a new --json flag to 'git status' that outputs repository state
>      > in a structured JSON format. This enables reliable machine parsing
>      > of status information for tools and automation.
>      >
>      > The JSON output includes:
>      > - Branch information (name, detached state, ahead/behind counts)
>      > - Staged files array
>      > - Unstaged files array
>      > - Untracked files array
>      > - Ignored files array (with --ignored flag)
>      >
>      > Implementation details:
>      > - Add STATUS_FORMAT_JSON to wt_status_format enum
>      > - Add JSON output option to git status and git commit
>      > - Implement JSON formatting helpers for arrays and branch info
>      > - Structure output for easy parsing and future extensibility
>      >
>      > Example:
>      >    $ git status --json
>      >    {
>      >      "branch": {
>      >        "current": "main",
>      >        "detached": false
>      >      },
>      >      "staged": ["file1.txt"],
>      >      "unstaged": ["file2.txt"],
>      >      "untracked": ["file3.txt"]
>      >    }
>      >
>      > This provides a robust alternative to parsing traditional output
>      > formats, making it easier to build reliable tools and automation
>      > around Git status information.
> >     What problem are you having paring the current output? You say this
>     patch provides a robust alternative but I'm afraid it does not seem to
>     handle filenames containing newlines or double quotes and it assumes
>     they are utf-8 encoded. It also seems to print a trailing comma if an
>     array has more than one item which is not allowed by the json spec. We
>     already have support for generating json output as documented
>     json-writer.h which will help with most of that but not the filename
>     encoding problem. New features should also be accompanied by tests to
>     prevent future regressions. I'm not opposed to adding json output if it
>     is robust and handles non utf-8 filenames but it would be helpful to
>     understand what problems you are having parsing the output of "git
>     status --porcelain[=v2]".
> >     Thanks
> >     Phillip
> >      > Signed-off-by: Tachera Sasi <[email protected]
>     <mailto:[email protected]>>
>      > Signed-off-by: tacherasasi <[email protected]
>     <mailto:[email protected]>>
>      > ---
>      >      status: add --json output format to git status
>      >
>      > Published-As: https://github.com/gitgitgadget/git/releases/tag/
>     pr-1937%2FtacheraSasi%2Ffeature%2Fjson-status-output-v1 <https://
>     github.com/gitgitgadget/git/releases/tag/
>     pr-1937%2FtacheraSasi%2Ffeature%2Fjson-status-output-v1>
>      > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git
>     <https://github.com/gitgitgadget/git> pr-1937/tacheraSasi/feature/
>     json-status-output-v1
>      > Pull-Request: https://github.com/gitgitgadget/git/pull/1937
>     <https://github.com/gitgitgadget/git/pull/1937>
>      >
>      >   builtin/commit.c |  9 ++++-
>      >   wt-status.c      | 98 +++++++++++++++++++++++++++++++++++++++++
>     +++++++
>      >   wt-status.h      |  1 +
>      >   3 files changed, 107 insertions(+), 1 deletion(-)
>      >
>      > diff --git a/builtin/commit.c b/builtin/commit.c
>      > index fba0dded64a..f1db4fdfd9a 100644
>      > --- a/builtin/commit.c
>      > +++ b/builtin/commit.c
>      > @@ -1540,6 +1540,9 @@ struct repository *repo UNUSED)
>      >               OPT_SET_INT(0, "long", &status_format,
>      >                           N_("show status in long format (default)"),
>      >                           STATUS_FORMAT_LONG),
>      > +             OPT_SET_INT(0, "json", &status_format,
>      > +                 N_("show status in JSON format"),
>      > +                 STATUS_FORMAT_JSON),
>      >               OPT_BOOL('z', "null", &s.null_termination,
>      >                        N_("terminate entries with NUL")),
>      >               {
>      > @@ -1603,7 +1606,8 @@ struct repository *repo UNUSED)
>      >                      prefix, argv);
>      >
>      >       if (status_format != STATUS_FORMAT_PORCELAIN &&
>      > -         status_format != STATUS_FORMAT_PORCELAIN_V2)
>      > +         status_format != STATUS_FORMAT_PORCELAIN_V2 &&
>      > +         status_format != STATUS_FORMAT_JSON)
>      >               progress_flag = REFRESH_PROGRESS;
>      >       repo_read_index(the_repository);
>      >       refresh_index(the_repository->index,
>      > @@ -1735,6 +1739,9 @@ int cmd_commit(int argc,
>      >               OPT_SET_INT(0, "long", &status_format,
>      >                           N_("show status in long format (default)"),
>      >                           STATUS_FORMAT_LONG),
>      > +             OPT_SET_INT(0, "json", &status_format,
>      > +                 N_("show status in JSON format"),
>      > +                 STATUS_FORMAT_JSON),
>      >               OPT_BOOL('z', "null", &s.null_termination,
>      >                        N_("terminate entries with NUL")),
>      >               OPT_BOOL(0, "amend", &amend, N_("amend previous
>     commit")),
>      > diff --git a/wt-status.c b/wt-status.c
>      > index 454601afa15..7192fb4d057 100644
>      > --- a/wt-status.c
>      > +++ b/wt-status.c
>      > @@ -2564,6 +2564,101 @@ static void wt_porcelain_v2_print(struct
>     wt_status *s)
>      >       }
>      >   }
>      >
>      > +
>      > +static void wt_json_print_string_array(struct wt_status *s,
>     const char *name, struct string_list *list)
>      > +{
>      > +     int i;
>      > +     fprintf(s->fp, "  \"%s\": [", name);
>      > +     for (i = 0; i < list->nr; i++) {
>      > +             if (i > 0)
>      > +                     fprintf(s->fp, ", ");
>      > +             fprintf(s->fp, "\"%s\"", list->items[i].string);
>      > +     }
>      > +     fprintf(s->fp, "]");
>      > +}
>      > +
>      > +static void wt_json_print_change_array(struct wt_status *s,
>     const char *name, int change_type)
>      > +{
>      > +     int i;
>      > +     struct string_list files = STRING_LIST_INIT_DUP;
>      > +
>      > +     for (i = 0; i < s->change.nr <http://change.nr>; i++) {
>      > +             struct wt_status_change_data *d;
>      > +             struct string_list_item *it;
>      > +             it = &(s->change.items[i]);
>      > +             d = it->util;
>      > +
>      > +             if ((change_type == WT_STATUS_UPDATED && d-
>      >index_status &&
>      > +                  d->index_status != DIFF_STATUS_UNMERGED) ||
>      > +                 (change_type == WT_STATUS_CHANGED && d-
>      >worktree_status &&
>      > +                  d->worktree_status != DIFF_STATUS_UNMERGED)) {
>      > +                     string_list_append(&files, it->string);
>      > +             }
>      > +     }
>      > +
>      > +     wt_json_print_string_array(s, name, &files);
>      > +     string_list_clear(&files, 0);
>      > +}
>      > +
>      > +static void wt_json_print_branch_info(struct wt_status *s)
>      > +{
>      > +     struct branch *branch;
>      > +     const char *branch_name;
>      > +     int ahead = 0, behind = 0;
>      > +
>      > +     fprintf(s->fp, "  \"branch\": {\n");
>      > +
>      > +     if (s->branch && !s->is_initial) {
>      > +             if (!strcmp(s->branch, "HEAD")) {
>      > +                     fprintf(s->fp, "    \"current\": \"HEAD\",\n");
>      > +                     fprintf(s->fp, "    \"detached\": true");
>      > +             } else {
>      > +                     if (skip_prefix(s->branch, "refs/heads/",
>     &branch_name)) {
>      > +                             fprintf(s->fp, "    \"current\":
>     \"%s\",\n", branch_name);
>      > +                             fprintf(s->fp, "    \"detached\":
>     false");
>      > +
>      > +                             branch = branch_get(branch_name);
>      > +                             if (branch && branch->merge &&
>     branch->merge[0] && branch->merge[0]->dst) {
>      > +                                     if (!
>     stat_tracking_info(branch, &ahead, &behind, NULL, 0, 0)) {
>      > +                                             fprintf(s->fp, ",
>     \n    \"ahead\": %d,\n    \"behind\": %d", ahead, behind);
>      > +                                     }
>      > +                             }
>      > +                     } else {
>      > +                             fprintf(s->fp, "    \"current\":
>     \"%s\",\n", s->branch);
>      > +                             fprintf(s->fp, "    \"detached\":
>     false");
>      > +                     }
>      > +             }
>      > +     } else {
>      > +             fprintf(s->fp, "    \"current\": null,\n");
>      > +             fprintf(s->fp, "    \"detached\": false");
>      > +     }
>      > +
>      > +     fprintf(s->fp, "\n  }");
>      > +}
>      > +
>      > +static void wt_json_status_print(struct wt_status *s)
>      > +{
>      > +     fprintf(s->fp, "{\n");
>      > +
>      > +     wt_json_print_branch_info(s);
>      > +     fprintf(s->fp, ",\n");
>      > +
>      > +     wt_json_print_change_array(s, "staged", WT_STATUS_UPDATED);
>      > +     fprintf(s->fp, ",\n");
>      > +
>      > +     wt_json_print_change_array(s, "unstaged", WT_STATUS_CHANGED);
>      > +     fprintf(s->fp, ",\n");
>      > +
>      > +     wt_json_print_string_array(s, "untracked", &s->untracked);
>      > +
>      > +     if (s->ignored.nr <http://ignored.nr> > 0) {
>      > +             fprintf(s->fp, ",\n");
>      > +             wt_json_print_string_array(s, "ignored", &s->ignored);
>      > +     }
>      > +
>      > +     fprintf(s->fp, "\n}\n");
>      > +}
>      > +
>      >   void wt_status_print(struct wt_status *s)
>      >   {
>      >       trace2_data_intmax("status", s->repo, "count/changed", s-
>      >change.nr <http://change.nr>);
>      > @@ -2583,6 +2678,9 @@ void wt_status_print(struct wt_status *s)
>      >       case STATUS_FORMAT_PORCELAIN_V2:
>      >               wt_porcelain_v2_print(s);
>      >               break;
>      > +     case STATUS_FORMAT_JSON:
>      > +             wt_json_status_print(s);
>      > +             break;
>      >       case STATUS_FORMAT_UNSPECIFIED:
>      >               BUG("finalize_deferred_config() should have been
>     called");
>      >               break;
>      > diff --git a/wt-status.h b/wt-status.h
>      > index 4e377ce62b8..e929af832b2 100644
>      > --- a/wt-status.h
>      > +++ b/wt-status.h
>      > @@ -74,6 +74,7 @@ enum wt_status_format {
>      >       STATUS_FORMAT_SHORT,
>      >       STATUS_FORMAT_PORCELAIN,
>      >       STATUS_FORMAT_PORCELAIN_V2,
>      > +     STATUS_FORMAT_JSON,
>      >
>      >       STATUS_FORMAT_UNSPECIFIED
>      >   };
>      >
>      > base-commit: cb3b40381e1d5ee32dde96521ad7cfd68eb308a6
> 

Copy link

gitgitgadget bot commented Jul 30, 2025

On the Git mailing list, Junio C Hamano wrote (reply to this):

"Tach via GitGitGadget" <[email protected]> writes:

> From: tacherasasi <[email protected]>

> Add a new --json flag to 'git status' that outputs repository state
> in a structured JSON format. This enables reliable machine parsing
> of status information for tools and automation.

The --porcelain format is meant to serve this "parseable for tools
and automation to read" use case.  Was there anything your parser
couldn't fathom out of the format?

> This provides a robust alternative to parsing traditional output
> formats, making it easier to build reliable tools and automation
> around Git status information.

The writer is obviously biased ;-) but I find this a bit hand-wavy
and unconvincing.

> Signed-off-by: Tachera Sasi <[email protected]>
> Signed-off-by: tacherasasi <[email protected]>

It is not like two separate people worked on this patch, or somebody
at ekilie worked on the patch but asked a different person with a
very similar name to send it on their behalf, right?

Perhaps update the user.{name,email} on the in-body From: to match
the one used on the first Sign-off, and drop the second Sign-off?

> diff --git a/builtin/commit.c b/builtin/commit.c
> index fba0dded64a..f1db4fdfd9a 100644
> --- a/builtin/commit.c
> +++ b/builtin/commit.c
> @@ -1540,6 +1540,9 @@ struct repository *repo UNUSED)
>  		OPT_SET_INT(0, "long", &status_format,
>  			    N_("show status in long format (default)"),
>  			    STATUS_FORMAT_LONG),
> +		OPT_SET_INT(0, "json", &status_format,
> +		    N_("show status in JSON format"),
> +		    STATUS_FORMAT_JSON),

There are now several ways to set to status_format now.  It could be
a good idea to make a preliminary UI clean-up to introduce a new
option "--format={short,long,porcelain,porcelain-v1,porcelain-v2}"
as a separate step, declare that any new format will use this new
option without adding a separate option on its own, before adding
any new features.  And then on top that, a new format can be
introduced without cluttering the set of options.

Not that I am enthused to see json here, given that we have
introduced porcelain format for the same purpose eons ago.

> @@ -1603,7 +1606,8 @@ struct repository *repo UNUSED)
>  		       prefix, argv);
>  
>  	if (status_format != STATUS_FORMAT_PORCELAIN &&
> -	    status_format != STATUS_FORMAT_PORCELAIN_V2)
> +	    status_format != STATUS_FORMAT_PORCELAIN_V2 &&
> +	    status_format != STATUS_FORMAT_JSON)
>  		progress_flag = REFRESH_PROGRESS;
>  	repo_read_index(the_repository);
>  	refresh_index(the_repository->index,

Also, we might want to tweak the bit-assignment for the
status_format constants to make this part easier to maintain (e.g.,
ones below 100 are for human consumption and gives progress bar and
ones above 100 are for machine consumption and suppresses progress,
or something like that), again as a separate preliminary clean-up
step before adding any new features.

> @@ -1735,6 +1739,9 @@ int cmd_commit(int argc,
>  		OPT_SET_INT(0, "long", &status_format,
>  			    N_("show status in long format (default)"),
>  			    STATUS_FORMAT_LONG),
> +		OPT_SET_INT(0, "json", &status_format,
> +		    N_("show status in JSON format"),
> +		    STATUS_FORMAT_JSON),

And if we are to clean-up with --format=<name> and build on top,
this part would become somewhat different.  The clean-up would
probably be somewhat like what opt_parse_porcelain() is doing and
"json" or "yaml" or any new format name would join if/else if/
cascade there.

> diff --git a/wt-status.c b/wt-status.c
> index 454601afa15..7192fb4d057 100644
> --- a/wt-status.c
> +++ b/wt-status.c
> @@ -2564,6 +2564,101 @@ static void wt_porcelain_v2_print(struct wt_status *s)
>  	}
>  }
>  
> +
> +static void wt_json_print_string_array(struct wt_status *s, const char *name, struct string_list *list)
> +{

We seem to have a json write-out library-ish helpers already; is it
too tied to its current use (i.e. trace output) and too inflexible
to be used here, or does the status output have so specialized and
unusual need as a normal JSON producing application that we need a
special purpose code here with bunch of fprintf() and manual
formatting?

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants